﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data.Utils
{
  internal abstract class SLWeakEventManager
  {
    protected SLWeakEventManager()
    {
    }

    protected static void SetCurrentManager( Type managerType, SLWeakEventManager manager )
    {
      if( managerType == null )
        throw new ArgumentNullException( "managerType" );

      if( manager == null )
        throw new ArgumentNullException( "manager" );

      SLWeakEventManager.Managers[ managerType ] = manager;
    }

    protected void DeliverEvent( object sender, EventArgs args )
    {
      if( sender == null )
        throw new ArgumentNullException( "sender" );

      if( args == null )
        throw new ArgumentNullException( "args" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to deliver an event using a manager other than the current one." );

      lock( this )
      {
        List<WeakReference> listeners = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == sender ) )
          {
            listeners = m_dictionary[ sourceReference ];
            break;
          }
        }

        if( listeners != null )
        {
          Type managerType = this.GetType();

          foreach( WeakReference listenerWeakRef in listeners.ToArray() )
          {
            if( listenerWeakRef.IsAlive && ( listenerWeakRef.Target != null ) )
            {
              ISLWeakEventListener listener = ( ISLWeakEventListener )listenerWeakRef.Target;
              listener.ReceiveWeakEvent( managerType, sender, args );
            }
            else
            {
              SLWeakEventManager.ScheduleCleanup();
            }
          }
        }
        else
        {
          SLWeakEventManager.ScheduleCleanup();
        }
      }
    }

    protected void AddListenerProtected( object source, ISLWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to add a listener using a manager other than the current one." );

      lock( this )
      {
        WeakReference weakRef = new WeakReference( listener );

        bool newSource = false;

        List<WeakReference> listeners = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == source ) )
          {
            listeners = m_dictionary[ sourceReference ];
            break;
          }
        }

        if( listeners == null )
        {
          listeners = new List<WeakReference>();
          m_dictionary.Add( new WeakReference( source ), listeners );
          listeners.Add( weakRef );
          newSource = true;
        }
        else
        {
          bool needAdd = true;
          foreach( WeakReference existingWeakRefs in listeners )
          {
            if( existingWeakRefs.IsAlive && ( existingWeakRefs.Target != null ) && ( existingWeakRefs.Target == listener ) )
            {
              needAdd = false;
              break;
            }
          }

          if( needAdd )
          {
            listeners.Add( weakRef );
          }
        }

        if( newSource )
        {
          this.StartListening( source );
        }

        SLWeakEventManager.ScheduleCleanup();
      }
    }

    protected void RemoveListenerProtected( object source, ISLWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to remove a listener using a manager other than the current one." );


      lock( this )
      {
        bool unregister = false;

        List<WeakReference> listeners = null;
        WeakReference sourceWeakRef = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == source ) )
          {
            listeners = m_dictionary[ sourceReference ];
            sourceWeakRef = sourceReference;
            break;
          }
        }

        if( listeners != null )
        {
          WeakReference removed = null;
          foreach( WeakReference weakRef in listeners )
          {
            if( weakRef.IsAlive && ( weakRef.Target != null ) && ( weakRef.Target == listener ) )
            {
              removed = weakRef;
              break;
            }
          }

          if( removed != null )
          {
            listeners.Remove( removed );
          }

          if( listeners.Count == 0 )
          {
            m_dictionary.Remove( sourceWeakRef );
            unregister = true;
          }
        }

        if( unregister )
        {
          this.StopListening( source );
        }

        SLWeakEventManager.ScheduleCleanup();
      }
    }

    protected abstract void StartListening( object source );

    protected abstract void StopListening( object source );

    private static void ScheduleCleanup()
    {
      lock( SLWeakEventManager.Managers )
      {
        if( CleanupDispatcherOperation == null )
        {
          try
          {
            Dispatcher dispatcher;

#if SILVERLIGHT
            dispatcher = Deployment.Current.Dispatcher;
#else
            dispatcher = Dispatcher.CurrentDispatcher;
#endif

            CleanupDispatcherOperation = dispatcher.BeginInvoke( new Action( SLWeakEventManager.Cleanup ) );
          }
          catch
          {
          }
        }
      }
    }

    private static void Cleanup()
    {
      lock( SLWeakEventManager.Managers )
      {
        List<WeakReference> toRemove = new List<WeakReference>();

        foreach( SLWeakEventManager manager in SLWeakEventManager.Managers.Values )
        {
          toRemove.Clear();

          foreach( KeyValuePair<WeakReference, List<WeakReference>> keyValuePair in manager.m_dictionary )
          {
            bool removeSource = false;

            if( !keyValuePair.Key.IsAlive )
            {
              removeSource = true;
            }

            int listenerCount = keyValuePair.Value.Count - 1;

            while( listenerCount >= 0 )
            {
              if( !keyValuePair.Value[ listenerCount ].IsAlive )
              {
                keyValuePair.Value.RemoveAt( listenerCount );
              }

              listenerCount--;
            }

            if( keyValuePair.Value.Count == 0 )
            {
              removeSource = true;
            }

            if( removeSource )
            {
              keyValuePair.Value.Clear();
              toRemove.Add( keyValuePair.Key );
            }
          }

          foreach( WeakReference removedSource in toRemove )
          {
            manager.m_dictionary.Remove( removedSource );
          }
        }

        SLWeakEventManager.CleanupDispatcherOperation = null;
      }
    }

    private static SLWeakEventManager GetCurrentManager( Type managerType )
    {
      SLWeakEventManager manager = null;
      if( !SLWeakEventManager.Managers.TryGetValue( managerType, out manager ) )
        return null;

      return manager;
    }

    private static Dictionary<Type, SLWeakEventManager> Managers = new Dictionary<Type, SLWeakEventManager>();
    private static DispatcherOperation CleanupDispatcherOperation;//null

    private Dictionary<WeakReference, List<WeakReference>> m_dictionary = new Dictionary<WeakReference, List<WeakReference>>();
  }
}
